package com.lagou.session.impl;

import com.lagou.pojo.Configuration;
import com.lagou.pojo.ErrorContext;
import com.lagou.pojo.MappedStatement;
import com.lagou.session.ExceptionFactory;
import com.lagou.session.Executor;
import com.lagou.session.SqlSession;

import java.lang.reflect.*;
import java.util.List;

@SuppressWarnings("unchecked")
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
    /**
     * 执行器
     */
    private Executor executor = new SimpleExecutor();

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<T> list = selectList(statementId, params);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: 查询返回结果过多", null);
        } else {
            return null;
        }
    }

    @Override
    public <T> List<T> selectList(String statementId, Object... params) throws Exception {
        try {
            MappedStatement mappedStatement = configuration.getMappedStatements().get(statementId);
            if (mappedStatement == null) {
                throw new RuntimeException("Can not find sql.");
            }
            ErrorContext.instance().id(statementId);
            return executor.query(configuration, mappedStatement, params);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: ", e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    @Override
    public <T> int insert(String statementId, Object... params) throws Exception {
        return update(statementId, params);
    }

    @Override
    public <T> int delete(String statementId, Object... params) throws Exception {
        return update(statementId, params);
    }

    @Override
    public <T> int update(String statementId, Object... params) throws Exception {
        try {
            MappedStatement mappedStatement = configuration.getMappedStatements().get(statementId);
            if (mappedStatement == null) {
                throw new RuntimeException("Can not find sql.");
            }
            ErrorContext.instance().id(statementId);
            return executor.update(configuration, mappedStatement, params);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error update database.  Cause: " + e, e);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    @Override
    public void close() {
        executor.close();
    }

    @Override
    public <T> T getMaper(Class<?> mapperClass) {
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都是执行JDBC代码 根据不同情况调用selectOne或selectList
                // 准备参数1: statementId 全限定名.方法名
                String methodName = method.getName();                       // 执行的方法名
                String className = method.getDeclaringClass().getName();    // mapper的全限定名
                String statementId = new StringBuilder(className).append(".").append(methodName).toString();
                MappedStatement mappedStatement = configuration.getMappedStatements().get(statementId);
                if (mappedStatement == null) {
                    throw new RuntimeException("Can not find sql.");
                }
                ErrorContext.instance().id(mappedStatement.getId());
                Object result;
                switch (mappedStatement.getSqlCommandType()) {
                    case SELECT : {
                        // 判断返回值类型是否进行类 泛型类型参数化
                        if (method.getGenericReturnType() instanceof ParameterizedType) {
                            result =  selectList(statementId, args);
                        } else {
                            result =  selectOne(statementId,args);
                        }
                        break;
                    }
                    case INSERT : {
                        result =  insert(statementId,args);
                        break;
                    }
                    case DELETE : {
                        result =  delete(statementId,args);
                        break;
                    }
                    case UPDATE: {
                        result =  update(statementId,args);
                        break;
                    }
                    default :
                        throw new RuntimeException("Unknown execution method for: " + mappedStatement.getSqlCommandType());
                }
                return result;
            }
        });
        return (T) proxyInstance;
    }

}
